/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ssp.topology;

import java.util.ArrayList;
import java.util.List;
import ssp.net.NWcomponentManager;
import ssp.utility.MersenneTwisterRNG;

/**
 * ネットワークトポロジを生成するクラス
 * @author yamazaki
 */
public class TopologyMaker {
    
    /**
     * ノードを管理するマネージャ
     */
    private static NWcomponentManager nodeManager = NWcomponentManager.getINSTANCE();
    
    /**
     * エッジを管理するマネージャ
     */
    private static EdgeManager edgeManager = EdgeManager.getINSTANCE();
    
    /**
     * 指定の2ノード間に指定の重みでリンクを張る
     * @param srcNode ノード
     * @param dstNode ノード
     * @param weight リンク重み
     */
    private static void createEdge(Node srcNode, Node dstNode, int weight) {
        if(srcNode == null || dstNode == null || weight <= 0) {
            throw new IllegalArgumentException();
        }
        srcNode.addNeighbor(dstNode);
        dstNode.addNeighbor(srcNode);
        edgeManager.add(new Edge(srcNode, dstNode, weight));
    }
    
    private static void deleteEdge(Edge edge) {
        Node srcNode = (Node)edge.getConnectedNodes().toArray()[0];
        Node dstNode = (Node)edge.getConnectedNodes().toArray()[1];
        srcNode.removeNeighbor(dstNode);
        dstNode.removeNeighbor(srcNode);
        edgeManager.remove(edge);
    }
    
    /**
     * 指定の2ノード間に重み1でリンクを張る
     * @param srcNode ノード
     * @param dstNode ノード
     */
    private static void createEdge(Node srcNode, Node dstNode) {
        createEdge(srcNode, dstNode, 1);
    }
    
    /**
     * 指定数の接続性を持つ1次元格子グラフを生成する
     * @param num 接続性
     */
    private static void makeOneDimLattice(int num) {
        if(num % 2 == 1) {
            throw new IllegalArgumentException("Argument has to be even number.");
        }
        for(int i=0 ; i<nodeManager.getNWcomponentNum() ; i++) {
            for(int j=1 ; j<=num/2 ; j++) {
                createEdge(nodeManager.getNWcomponent(i), nodeManager.getNWcomponent((i+j) % nodeManager.getNWcomponentNum()));
            }
        }
    }
    
    /**
     * スモールワールドモデル（WSモデル）を生成する<br>
     * prob=1の時はランダムグラフとなる
     * @param linkNum 1次元格子における接続性
     * @param prob 1次元格子からリンク交換が発生する確率
     */
    public static void makeWSmodel(int linkNum, double prob) {
        MersenneTwisterRNG rng = MersenneTwisterRNG.getINSTANCE();
        makeOneDimLattice(linkNum);
        
        List<Edge> edgeList = new ArrayList<Edge>(edgeManager.getEdgeList());
        
        for(Edge edge : edgeList) {
            if(rng.nextBoolean(prob)) {
                deleteEdge(edge);
                while(true){
                    List<Node> newNodes = new ArrayList<Node>(nodeManager.getShuffledList(2));
                    Edge newEdge = new Edge(newNodes.get(0), newNodes.get(1));
                    if(!edgeManager.isIncludeEdge(newEdge)) {
                        edgeManager.add(newEdge);
                        break;
                    }
                }
            }
        }
    }
    
    /**
     * 指定のノード数の完全グラフを生成する
     * @param linkNum ノード数
     */
    private static void makeCompleteGraph(int nodeNum) {
        if(nodeNum < 2) {
            throw new IllegalArgumentException();
        }
        for(int i=0 ; i< nodeNum ; i++) {
            for(int j=i+1 ; j<nodeNum ; j++) {
                createEdge(nodeManager.getNWcomponent(i), nodeManager.getNWcomponent(j));
            }
        }
    }
    
    /**
     * 指定の最低リンク数に基づいてスケールフリーモデル（BAモデル）を生成する
     * @param linkNum 最低リンク数
     */
    public static void makeBAmodel(int linkNum) {
        makeCompleteGraph(linkNum);
        MersenneTwisterRNG rng = MersenneTwisterRNG.getINSTANCE();
        
        for(int i=linkNum ; i<nodeManager.getNWcomponentNum() ; i++) {
            Node newNode = nodeManager.getNWcomponent(i);
            for(int j=0; j<linkNum ; /** none **/) {
                Edge selectedEdge = edgeManager.getEdge(rng.nextInt(edgeManager.getEdgeNum()));
                if(selectedEdge.getConnectedNodes().contains(newNode)) {
                    continue;
                }
                Node selectedNode = (Node)selectedEdge.getConnectedNodes().toArray()[rng.nextInt(2)];
                if(!newNode.isNeighbor(selectedNode)) {
                    createEdge(newNode, selectedNode);
                    j++;
                }
            }
        }
    }
    
}
